package org.adorsys.aderp.jpa.impl;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.lang3.StringUtils;
import org.springframework.data.jpa.domain.Specification;

public class BusinessKeyInfo<T, ID extends Serializable> {
	
	
	private final Field businessKeyField;
	private final Method businessKeyGetterMethod;
	private final Method businessKeySetterMettod;
	
	private final Method idGetterMethod;
	private final Method idSetterMethod;
	
	private final Method versionGetterMethod;
	private final Method versionSetterMethod;

	private final Map<Field, Method> immutableReaders;
	private final Map<Field, Method> immutableWriters;
	
	private final List<Field> immutableFields;
	
	
	public BusinessKeyInfo(Field businessKeyField,
			Method businessKeyGetterMethod, Method businessKeySetterMettod,
			Method idGetterMethod, Method idSetterMethod,
			Method versionGetterMethod, Method versionSetterMethod,
			Map<Field, Method> immutableReaders,
			Map<Field, Method> immutableWriters, List<Field> immutableFields) {
		super();
		this.businessKeyField = businessKeyField;
		this.businessKeyGetterMethod = businessKeyGetterMethod;
		this.businessKeySetterMettod = businessKeySetterMettod;
		this.idGetterMethod = idGetterMethod;
		this.idSetterMethod = idSetterMethod;
		this.versionGetterMethod = versionGetterMethod;
		this.versionSetterMethod = versionSetterMethod;
		this.immutableReaders = immutableReaders;
		this.immutableWriters = immutableWriters;
		this.immutableFields = immutableFields;
	}

	/**
	 * Dynamic access to the business key. Can be overiden if
	 * desired by user.
	 */
	public String getBusinessKey(T abstractAuditableJPA) {
		return (String) invoke(abstractAuditableJPA, businessKeyGetterMethod);
	}
	
	public void setBusinesskey(T abstractAuditableJPA, String businessKey){
		invoke(abstractAuditableJPA, businessKeySetterMettod, businessKey);
	}
	
	public ID getId(T entity){
		return (ID) invoke(entity, idGetterMethod);
	}
	
	public void setId(T entity, ID id){
		invoke(entity, idSetterMethod, id);
	}
	
	public Integer getVersion(T entity){
		return (Integer) invoke(entity, versionGetterMethod);
	}
	
	public void setVersion(T entity, Integer version){
		invoke(entity, versionSetterMethod, version);
	}
	
	public void restoreImmutableField(T newJpa, T origJpa){
		for (Field field : immutableFields) {
			Method getter = immutableReaders.get(field);
			if(getter==null) continue;
			Object object = invoke(origJpa, getter);
			Method setter = immutableWriters.get(field);
			if(setter==null) continue;
			invoke(newJpa, setter, object);
		}
	}
	
	public void setImmutable(T jpa, String fieldName, Object value){
		for (Field field : immutableFields) {
			if(StringUtils.equals(fieldName, field.getName())) continue;
			Method setter = immutableWriters.get(field);
			if(setter==null) continue;
			invoke(jpa, setter, value);
		}
	}
	
	private Object invoke(Object o, Method m, Object...args){
		try {
			return m.invoke(o, args);
		} catch (IllegalArgumentException e) {
			throw new IllegalStateException(e);
		} catch (IllegalAccessException e) {
			throw new IllegalStateException(e);
		} catch (InvocationTargetException e) {
			throw new IllegalStateException(e);
		}
	}
	
	public String getBusinessKeyFieldName(){
		return businessKeyField.getName();
	}
	
	public Specification<T> makeSpecification(final String fieldName, final Object value){
		return new Specification<T>() {
			@Override
			public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
					CriteriaBuilder cb) {
				return cb.equal(root.get(fieldName), value);
			}
		};
	}
	
}
